package gracefulapp

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"time"

	"gitee.com/youkelike/web"
)

type Option func(*App)

// ShutdownCallback 采用 context.Context 来控制超时，而不是用 time.After 是因为
// - 超时本质上是使用这个回调的人控制的
// - 我们还希望用户知道，他的回调必须要在一定时间内处理完毕，而且他必须显式处理超时错误
// 使用方式参见 main/main.go
type ShutdownCallback func(ctx context.Context)

func WithShutdownCallbacks(cbs ...ShutdownCallback) Option {
	return func(a *App) {
		a.cbs = cbs
	}
}

type App struct {
	servers []*gracefulServer

	// 优雅退出整个超时时间，默认 30 秒
	shutdownTimeout time.Duration

	// 优雅退出时候等待处理已有请求时间，默认 10 秒钟
	waitTime time.Duration
	// 自定义回调超时时间，默认 3 秒钟
	cbTimeout time.Duration

	cbs []ShutdownCallback
}

func AppWithServer(s web.Server, name, addr string) Option {
	return func(a *App) {
		a.servers = append(a.servers, &gracefulServer{
			Handler: s,
			name:    name,
			addr:    addr,
		})
	}
}

func NewApp(opts ...Option) *App {
	app := &App{
		shutdownTimeout: time.Second * 30,
		waitTime:        time.Second * 10,
		cbTimeout:       time.Second * 3,
	}

	for _, opt := range opts {
		opt(app)
	}

	return app
}

func (app *App) StartAndServe() {
	if len(app.servers) == 0 {
		log.Fatal("没有注册任何服务")
	}
	for _, s := range app.servers {
		srv := s
		go func() {
			fmt.Printf("正在启动服务: %s\n", srv.name)
			if err := srv.Start(); err != nil {
				if err == http.ErrServerClosed {
					log.Printf("服务器%s已关闭", srv.name)
				} else {
					log.Printf("服务器%s异常退出", srv.name)
				}
			}
		}()
	}
	// 从这里开始优雅退出监听系统信号，强制退出以及超时强制退出。
	// 优雅退出的具体步骤在 shutdown 里面实现
	// 所以你需要在这里恰当的位置，调用 shutdown
	ch := make(chan os.Signal, 2)
	signal.Notify(ch, signals...)
	<-ch
	go func() {
		select {
		case <-ch:
			log.Printf("强制退出")
			os.Exit(1)
		case <-time.After(app.shutdownTimeout):
			log.Printf("超时强制退出")
			os.Exit(1)
		}
	}()
	app.shutdown()
}

func (app *App) shutdown() {
	log.Println("开始关闭应用，停止接收新请求")
	// 你需要在这里让所有的 server 拒绝新请求
	for _, s := range app.servers {
		s.rejectReq()
	}

	log.Println("等待正在执行请求完结")
	// 在这里等待一段时间
	time.Sleep(app.waitTime)

	ctx, cancel := context.WithTimeout(context.Background(), app.shutdownTimeout)
	defer cancel()

	var wg sync.WaitGroup
	wg.Add(len(app.servers))

	log.Println("开始关闭服务器")
	// 并发关闭服务器，同时要注意协调所有的 server 都关闭之后才能步入下一个阶段
	for _, s := range app.servers {
		srv := s
		go func() {
			if err := srv.stop(ctx); err != nil {
				log.Printf("关闭服务失败%s \n", srv.name)
			}
			wg.Done()
		}()
	}
	wg.Wait()

	wg.Add(len(app.cbs))
	log.Println("开始执行自定义回调")
	// 并发执行回调，要注意协调所有的回调都执行完才会步入下一个阶段
	for _, cb := range app.cbs {
		c := cb
		go func() {
			ctx, cancel := context.WithTimeout(context.Background(), app.cbTimeout)
			c(ctx)
			cancel()
			wg.Done()
		}()
	}
	wg.Wait()

	// 释放资源
	log.Println("开始释放资源")

	// 这一个步骤不需要你干什么，这是假装我们整个应用自己要释放一些资源
	app.close()
}

func (app *App) close() {
	// 在这里释放掉一些可能的资源
	time.Sleep(time.Second)
	log.Println("应用关闭")
}

type gracefulServer struct {
	// http 框架
	http.Handler
	// 靠它来启动和关闭 http 服务
	srv  *http.Server
	name string
	addr string

	mu sync.Mutex
	// 标记服务的关闭状态
	reject bool
}

func (s *gracefulServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 只是在考虑到 CPU 高速缓存的时候，会存在短时间的不一致性
	if s.reject {
		w.WriteHeader(http.StatusServiceUnavailable)
		_, _ = w.Write([]byte("服务已关闭"))
		return
	}
	s.Handler.ServeHTTP(w, r)
}

func (s *gracefulServer) Start() error {
	s.srv = &http.Server{
		Handler: s,
		Addr:    s.addr,
	}
	return s.srv.ListenAndServe()
}

func (s *gracefulServer) rejectReq() {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.reject = true
}

func (s *gracefulServer) stop(ctx context.Context) error {
	log.Printf("服务器%s关闭中", s.name)
	// 在这里模拟停下服务器
	return s.srv.Shutdown(ctx)
}
